#[[
    高级IO
        非阻塞IO & 阻塞IO
        阻塞IO是把这件事情做到为止, 读这个设备(或者读一个文件), 读不到内容, 我就不走。
        非阻塞IO是做任何一件事情都是尝试来做, 读一个文件, 如果读不到内容, 那我也返回。

        阻塞的系统调用会被信号打断, 返回 EINTR(error interrupt), 这是假错,
        不是当前的调用失败了, 而是你是一个阻塞的系统调用, 你总做不到你要完成的事情。
        此时一个信号来了, 就会打断你当前的这个系统调用。

        还有一个假错的现象是 EAGAIN(error again): 例如: read();
        如果当前的操作方式是非阻塞方式的话(即打开非阻塞的文件描述符时),
        那么有可能发生 EAGAIN 的假错。
        什么是 EAGAIN ? 例如: 我在读一个非阻塞的文件描述符,
        我读一个阻塞的文件描述符的时候, 就是读这个fd, 知道读到内容为止,
        这是阻塞。
        非阻塞是, 读一个非阻塞的fd, 一读如果有内容那就返回;
        一读如果没有内容那也返回, 但是这个返回会当做一个出错的返回,
        查看 errno 的话, 发现 errno 是 EAGAIN , 这个返回不是真正意义上的出错。
        是因为我做这件事了, 当时没有成功。只是因为现在fd没有数据,
        不是我当前的read();这个函数调用的问题。

    有限状态机(Finite State Machine)
    finite state machine
    有限状态机编程。

    1. 非阻塞IO
    2. IO多路转接(IO多路复用)(其实就是对文件描述符的监视, 当fd上有内容可读的时候,
    通知我, 我再去读。这样就解决了盲等现象。)
    在1.主题中的程序都相当于 "盲等" , 我不停的查询(轮询)。
    这2.主题中可以解决这个 "盲等" 问题, 加上文件描述符的监视, 将查询改为通知。
    3. 高级IO读写函数
    4. 存储映射IO
    5. 文件锁

]]

#[[
非阻塞IO


数据中继引擎:
现在打开了两个设备(两个设备、两个管道、两个socket、两个磁盘上的文件、等等),
使用文件描述符来操作这个两个设备。
现在用这两个设备进行数据交换, 一个专业的叫法叫做数据中继(data relay(接力赛/中继设备/转送)),
其实就是将一个文件描述符中的内容拿到另一个文件描述符中, 在这个文件描述符中进行加工、计算等操作后,
然后再把数据拿回之前的文件描述符中去。

1. rl -> wr -> rr -> wl
因为如果 rl 的时候, 没有内容就会一直阻塞。
其实可以这样
2. rl -> wr
   rr -> wl
这样就相当于 MyCopy。


数据中继引擎放大 -> 中间人(拦截服务, 代理服务等)
代理服务器, 负载均衡(通过IP访问这个IP, 客户端访问的这个IP的主机并不是一个主机,
这个主机后有一个主机集群, 这个IP的服务器程序拿到请求后, 看背后的服务器集群, 谁
的负载小, 然后就让这个主机上的服务器程序处理。对外, 大家只认为只有我在工作,
其实我背后有一个集群在工作。例如: Nginx)。

这是两个文件描述符进行通信, 放大的话可以是两个用户, 两个服务器等等。


---

有限状态机编程可以解决复杂流程的问题。

ctrl + alt + F5/F6, 用这两个终端来表示两个设备。
按 ctrl + alt + F5/F6, 后输入登录的用户ID和用户password, 即成功登录了两个终端,
使用 who 可以查看。在 Ubuntu 20 中按 ctrl + alt + F2 可以返回图形化界面。

liuyou@liuyou-virtual-machine:~/CLionProjects/LinuxEnvironmentProgramming2/src/concurrence/parallel/thread$ who
liuyou   :0           2021-09-21 20:08 (:0)
liuyou   tty5         2021-11-01 13:21
liuyou   tty6         2021-11-01 13:21
liuyou@liuyou-virtual-machine:~/CLionProjects/LinuxEnvironmentProgramming2/src/concurrence/parallel/thread$

]]
add_executable(datarelay1 nonblock/datarelay1.cpp)

add_subdirectory(relayer)

#[[
任务如果是IO密集型的任务, 对于IO密集型的任务, 可以对IO进行多路转接(即监视fd),
IO多路转接(IO多路复用)(其实就是对文件描述符的监视, 当fd上有内容可读的时候,
通知我, 我再去读。这样就解决了盲等现象。)

IO多路转接: 监视当前fd, 当fd发生了我感兴趣的行为的时候(我在之前已经布置了哪些行为是感兴趣的。),
我才去做后续操作, 例如: 我感兴趣的写可以了, 我就去写; 我感兴趣的读可以了, 我就去读。

IO多路转接, 监视fd, 同时可以监视多个fd。


select();
    select();移植性非常好, 是一个古老的函数。但是效率啥的都没有, 除了古老没有别的好处。
    而且 select();接口设计的有缺陷, 传参是有问题的。
    select();可以完成一个安全可靠的休眠, 取代 sleep();。
    select(-1, nullptr, nullptr, nullptr, /* 设置超时时间, 即休眠时间 */);
    这样就可以实现一个休眠。
    select(..., ..., ..., ..., nullptr);这样就是死等/盲等, 阻塞的。

    select();设计的缺陷就是这 三个集合的参数。
    因为select();回填了这三个参数, 之前我布置的感兴趣的文件描述符的集合已经被覆盖了。


poll();
    select();和poll();用来监视文件描述符的行为, 这两个函数的组织思路是完全不一致的。
    select();是以事件为单位组织文件描述符, poll();是以文件描述符为单位组织事件。
    select();和poll();的出发点和组织的形式是不一样的。
    poll();也能够移植。
epoll();
    很多平台觉得 poll();的效率不够高, 所以很多平台在 poll();的基础上设计了自己平台的
    方言, linux平台的方言为 epoll(); (effective poll)。
    epoll();和poll();的思路相似, 组织形式也一样。
    只不过poll();是用户自己来维护一些内容, epoll();把poll();用户使用到的数据结构等
    内容进行了封装。
    epoll();不能移植。在 man手册 第7章。

这三个函数功能都是完成IO多路转接, 即对fd的监视。

]]

#[[
SELECT(2)                                                                       Linux Programmer's Manual                                                                       SELECT(2)

NAME
       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
       同步IO复用。

SYNOPSIS
       /* According to POSIX.1-2001, POSIX.1-2008 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       // nfds: 文件描述符的个数, 但是不是让我写想监听多少个文件描述符。
       // 例如: 我监视4个文件描述符, 然后传入参数 4 , 这是不对的。
       // 而是传入你所监视的文件描述符当中最大的那个然后再加1。
       // 比如: 当前想监视文件描述符 3 5 7 9 , 那么就传入 nfds 10。
       // 比如: 想要监视的最大的文件描述符为 1002, 那么就要传入 1003。
       //
       // 然后第2 3 4 个参数是 fd_set。
       // readfds: 你所关心的可以发生读状态的文件描述符都放在这个集合当中。
       // writefds: 所关心的写的文件描述符的集合。
       // exceptfds: 所关心的异常的文件描述符的集合。
       //
       // 最后一个参数 timeout: 超时。
       // 如果select();如果没有设置 timeout, 那么它就是死等,
       // 等到感兴趣的事件发生, 如果readfds中有文件描述符可读了,
       // 那么select();就会返回; 或者 writefds中的某个文件描述符可以写了,
       // 函数也会返回。
       //
       // 如果不进行超时设置, 这个函数就是阻塞的, 知道等到感兴趣的事件发生为止(即被监视的fd变化为止)。
       //
       // timeout的类型是 timeval结构体类型
          struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };
       // 时间控制是 秒 + 微妙(10的-6次方)。


       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       // 下面这几个函数是对 fd_set 进行操作。

       // 在 set 文件描述符集合当中, 把 fd 这个文件描述符删除。
       void FD_CLR(int fd, fd_set *set);
       // 判断 fd 是否在 set中。
       int  FD_ISSET(int fd, fd_set *set);
       // 往 set 文件描述符集合中, 添加 fd 这个文件描述符。
       void FD_SET(int fd, fd_set *set);
       // 清空一个文件描述符集合。
       void FD_ZERO(fd_set *set);

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       pselect(): _POSIX_C_SOURCE >= 200112L

DESCRIPTION
       select()  and  pselect()  allow  a  program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation
       (e.g., input possible).  A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2), or a sufficiently small write(2))  with‐
       out blocking.
       select() 和 pselect() 允许程序监视多个文件描述符，等待一个或多个文件描述符“准备好”进行某类 IO 操作（例如，可能的输入）。如果可以在不阻塞的情况下执行相应的 IO 操作（例如，read(2) 或足够小的 write(2)），则认为文件描述符已准备就绪。

       select() can monitor only file descriptors numbers that are less than FD_SETSIZE; poll(2) does not have this limitation.  See BUGS.
       select() 只能监视小于 FD_SETSIZE 的文件描述符数； poll(2) 没有这个限制。见BUG。

       The operation of select() and pselect() is identical, other than these three differences:
       select() 和 pselect() 的操作是一样的，除了这三个区别:

       (i)    select() uses a timeout that is a struct timeval (with seconds and microseconds), while pselect() uses a struct timespec (with seconds and nanoseconds).

       (ii)   select() may update the timeout argument to indicate how much time was left.  pselect() does not change this argument.

       (iii)  select() has no sigmask argument, and behaves as pselect() called with NULL sigmask.

       Three independent sets of file descriptors are watched.  The file descriptors listed in readfds will be watched to see if characters become available for reading (more precisely,
       to see if a read will not block; in particular, a file descriptor is also ready on end-of-file).  The file descriptors in writefds will be watched to see if  space  is  available
       for  write  (though  a large write may still block).  The file descriptors in exceptfds will be watched for exceptional conditions.  (For examples of some exceptional conditions,
       see the discussion of POLLPRI in poll(2).)

       On exit, each of the file descriptor sets is modified in place to indicate which file descriptors actually changed status.  (Thus, if using select() within a loop, the sets  must
       be reinitialized before each call.)

       Each of the three file descriptor sets may be specified as NULL if no file descriptors are to be watched for the corresponding class of events.

       Four  macros are provided to manipulate the sets.  FD_ZERO() clears a set.  FD_SET() and FD_CLR() add and remove a given file descriptor from a set.  FD_ISSET() tests to see if a
       file descriptor is part of the set; this is useful after select() returns.

       nfds should be set to the highest-numbered file descriptor in any of the three sets, plus 1.  The indicated file descriptors in each set are checked, up to this  limit  (but  see
       BUGS).

       The timeout argument specifies the interval that select() should block waiting for a file descriptor to become ready.  The call will block until either:

       *  a file descriptor becomes ready;

       *  the call is interrupted by a signal handler; or

       *  the timeout expires.

       Note that the timeout interval will be rounded up to the system clock granularity, and kernel scheduling delays mean that the blocking interval may overrun by a small amount.  If
       both fields of the timeval structure are zero, then select() returns immediately.  (This is useful for polling.)  If timeout is NULL (no  timeout),  select()  can  block  indefi‐
       nitely.

       sigmask  is  a pointer to a signal mask (see sigprocmask(2)); if it is not NULL, then pselect() first replaces the current signal mask by the one pointed to by sigmask, then does
       the "select" function, and then restores the original signal mask.

       Other than the difference in the precision of the timeout argument, the following pselect() call:

           ready = pselect(nfds, &readfds, &writefds, &exceptfds,
                           timeout, &sigmask);

       is equivalent to atomically executing the following calls:

           sigset_t origmask;

           pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
           ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
           pthread_sigmask(SIG_SETMASK, &origmask, NULL);

       The reason that pselect() is needed is that if one wants to wait for either a signal or for a file descriptor to become ready, then an atomic test is needed to prevent race  con‐
       ditions.   (Suppose  the  signal handler sets a global flag and returns.  Then a test of this global flag followed by a call of select() could hang indefinitely if the signal ar‐
       rived just after the test but just before the call.  By contrast, pselect() allows one to first block signals, handle the signals that have come in, then call pselect() with  the
       desired sigmask, avoiding the race.)

   The timeout
       The time structures involved are defined in <sys/time.h> and look like

           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

       and

           struct timespec {
               long    tv_sec;         /* seconds */
               long    tv_nsec;        /* nanoseconds */
           };

       (However, see below on the POSIX.1 versions.)

       Some code calls select() with all three sets empty, nfds zero, and a non-NULL timeout as a fairly portable way to sleep with subsecond precision.

       On  Linux, select() modifies timeout to reflect the amount of time not slept; most other implementations do not do this.  (POSIX.1 permits either behavior.)  This causes problems
       both when Linux code which reads timeout is ported to other operating systems, and when code is ported to Linux that reuses a struct timeval for  multiple  select()s  in  a  loop
       without reinitializing it.  Consider timeout to be undefined after select() returns.

RETURN VALUE
       On  success,  select()  and  pselect()  return  the  number of file descriptors contained in the three returned descriptor sets (that is, the total number of bits that are set in
       readfds, writefds, exceptfds) which may be zero if the timeout expires before anything interesting happens.  On error, -1 is returned, and errno is set to indicate the error; the
       file descriptor sets are unmodified, and timeout becomes undefined.
       成功时，select() 和 pselect() 返回三个返回的描述符集合中包含的文件描述符的数量（即在 readfds、writefds、exceptfds 中设置的总位数），
       如果超时到期可能为零在任何有趣的事情发生之前。出错时返回-1，设置errno表示出错；文件描述符集未修改，超时未定义。

       select();调用成功返回的是 发生了我感兴趣的文件描述符的个数, 而发生了感兴趣的文件描述符
       放到哪了呢? 依然放到了第 2 3 4 个参数的集合当中。
       如果失败的话, 返回值为 -1, 并且设置 errno。
       如果设置了 timeout 的话, 到时后函数返回。

ERRORS
       EBADF  An invalid file descriptor was given in one of the sets.  (Perhaps a file descriptor that was already closed, or one on which an error has occurred.)  However, see BUGS.

       EINTR  A signal was caught; see signal(7).

       EINVAL nfds is negative or exceeds the RLIMIT_NOFILE resource limit (see getrlimit(2)).

       EINVAL The value contained within timeout is invalid.

       ENOMEM Unable to allocate memory for internal tables.

VERSIONS
       pselect() was added to Linux in kernel 2.6.16.  Prior to this, pselect() was emulated in glibc (but see BUGS).

CONFORMING TO
       select()  conforms  to POSIX.1-2001, POSIX.1-2008, and 4.4BSD (select() first appeared in 4.2BSD).  Generally portable to/from non-BSD systems supporting clones of the BSD socket
       layer (including System V variants).  However, note that the System V variant typically sets the timeout variable before exit, but the BSD variant does not.

       pselect() is defined in POSIX.1g, and in POSIX.1-2001 and POSIX.1-2008.

NOTES
       An fd_set is a fixed size buffer.  Executing FD_CLR() or FD_SET() with a value of fd that is negative or is equal to or larger than FD_SETSIZE will result in undefined  behavior.
       Moreover, POSIX requires fd to be a valid file descriptor.

       The operation of select() and pselect() is not affected by the O_NONBLOCK flag.

       On  some other UNIX systems, select() can fail with the error EAGAIN if the system fails to allocate kernel-internal resources, rather than ENOMEM as Linux does.  POSIX specifies
       this error for poll(2), but not for select().  Portable programs may wish to check for EAGAIN and loop, just as with EINTR.

       On systems that lack pselect(), reliable (and more portable) signal trapping can be achieved using the self-pipe trick.  In this technique, a signal handler writes a  byte  to  a
       pipe  whose other end is monitored by select() in the main program.  (To avoid possibly blocking when writing to a pipe that may be full or reading from a pipe that may be empty,
       nonblocking I/O is used when reading from and writing to the pipe.)

       Concerning the types involved, the classical situation is that the two fields of a timeval structure are typed as  long  (as  shown  above),  and  the  structure  is  defined  in
       <sys/time.h>.  The POSIX.1 situation is

           struct timeval {
               time_t         tv_sec;     /* seconds */
               suseconds_t    tv_usec;    /* microseconds */
           };

       where the structure is defined in <sys/select.h> and the data types time_t and suseconds_t are defined in <sys/types.h>.

       Concerning prototypes, the classical situation is that one should include <time.h> for select().  The POSIX.1 situation is that one should include <sys/select.h> for select() and
       pselect().

       Under glibc 2.0, <sys/select.h> gives the wrong prototype for pselect().  Under glibc 2.1 to 2.2.1, it gives pselect() when _GNU_SOURCE is defined.  Since glibc  2.2.2,  the  re‐
       quirements are as shown in the SYNOPSIS.

   Correspondence between select() and poll() notifications
       Within  the  Linux  kernel  source, we find the following definitions which show the correspondence between the readable, writable, and exceptional condition notifications of se‐
       lect() and the event notifications provided by poll(2) and epoll(7):

           #define POLLIN_SET  (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN |
                                EPOLLHUP | EPOLLERR)
                              /* Ready for reading */
           #define POLLOUT_SET (EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT |
                                EPOLLERR)
                              /* Ready for writing */
           #define POLLEX_SET  (EPOLLPRI)
                              /* Exceptional condition */

   Multithreaded applications
       If a file descriptor being monitored by select() is closed in another thread, the result is unspecified.  On some UNIX systems, select() unblocks and returns, with an  indication
       that  the  file  descriptor is ready (a subsequent I/O operation will likely fail with an error, unless another process reopens file descriptor between the time select() returned
       and the I/O operation is performed).  On Linux (and some other systems), closing the file descriptor in another thread has no effect on select().   In  summary,  any  application
       that relies on a particular behavior in this scenario must be considered buggy.

   C library/kernel differences
       The  Linux  kernel  allows file descriptor sets of arbitrary size, determining the length of the sets to be checked from the value of nfds.  However, in the glibc implementation,
       the fd_set type is fixed in size.  See also BUGS.

       The pselect() interface described in this page is implemented by glibc.  The underlying Linux system call is named pselect6().  This system call has somewhat  different  behavior
       from the glibc wrapper function.

       The  Linux  pselect6() system call modifies its timeout argument.  However, the glibc wrapper function hides this behavior by using a local variable for the timeout argument that
       is passed to the system call.  Thus, the glibc pselect() function does not modify its timeout argument; this is the behavior required by POSIX.1-2001.

       The final argument of the pselect6() system call is not a sigset_t * pointer, but is instead a structure of the form:

           struct {
               const kernel_sigset_t *ss;   /* Pointer to signal set */
               size_t ss_len;               /* Size (in bytes) of object
                                               pointed to by 'ss' */
           };

       This allows the system call to obtain both a pointer to the signal set and its size, while allowing for the fact that most architectures support a maximum of  6  arguments  to  a
       system call.  See sigprocmask(2) for a discussion of the difference between the kernel and libc notion of the signal set.

BUGS
       POSIX  allows an implementation to define an upper limit, advertised via the constant FD_SETSIZE, on the range of file descriptors that can be specified in a file descriptor set.
       The Linux kernel imposes no fixed limit, but the glibc implementation makes fd_set a fixed-size type, with FD_SETSIZE defined as 1024, and the FD_*() macros  operating  according
       to that limit.  To monitor file descriptors greater than 1023, use poll(2) instead.

       The  implementation  of  the  fd_set arguments as value-result arguments means that they must be reinitialized on each call to select().  This design error is avoided by poll(2),
       which uses separate structure fields for the input and output of the call.

       According to POSIX, select() should check all specified file descriptors in the three file descriptor sets, up to the limit nfds-1.  However, the current  implementation  ignores
       any  file descriptor in these sets that is greater than the maximum file descriptor number that the process currently has open.  According to POSIX, any such file descriptor that
       is specified in one of the sets should result in the error EBADF.

       Glibc 2.0 provided a version of pselect() that did not take a sigmask argument.

       Starting with version 2.1, glibc provided an emulation of pselect() that was implemented using sigprocmask(2) and select().  This implementation remained vulnerable to  the  very
       race condition that pselect() was designed to prevent.  Modern versions of glibc use the (race-free) pselect() system call on kernels where it is provided.

       Under  Linux,  select()  may report a socket file descriptor as "ready for reading", while nevertheless a subsequent read blocks.  This could for example happen when data has ar‐
       rived but upon examination has wrong checksum and is discarded.  There may be other circumstances in which a file descriptor is spuriously reported as  ready.   Thus  it  may  be
       safer to use O_NONBLOCK on sockets that should not block.

       On  Linux,  select()  also modifies timeout if the call is interrupted by a signal handler (i.e., the EINTR error return).  This is not permitted by POSIX.1.  The Linux pselect()
       system call has the same behavior, but the glibc wrapper hides this behavior by internally copying the timeout to a local variable and passing that variable to the system call.

SEE ALSO
       accept(2), connect(2), poll(2), read(2), recv(2), restart_syscall(2), send(2), sigprocmask(2), write(2), epoll(7), time(7)

       For a tutorial with discussion and examples, see select_tut(2).

COLOPHON
       This page is part of release 5.05 of the Linux man-pages project.  A description of the project, information about reporting bugs, and the latest version of  this  page,  can  be
       found at https://www.kernel.org/doc/man-pages/.

Linux                                                                                   2019-11-19                                                                              SELECT(2)

]]


#[[
使用select();poll();epoll();
1. 布置监视任务
2. 监视, 即调用 select();poll();epoll();
3. 查看监视结果, 根据结果...

]]
add_executable(datarelay2 nonblock/datarelay2.cpp)

#[[
select();的缺点:
1. select();布置监视任务的位置就是第 2 3 4 个参数, 而监视结果所存放的位置也是这三个集合。
它的监视现场和监视结果存放的是同一块空间。
2. 第一个参数, 第一个参数类型设计为了 int, 这就对监视的最大文件描述符有一个限制。
3. select();监视的事件太单一: 除了读, 写, 出错就不监视其它事件类型了。

select();的特点:
select();做了三个集合: readfds, writefds, exceptfds, 即
select();是 以事件为单位来组织文件描述符, 读的事件的fd的集合, 写的事件的fd的集合,
出错事件的fd的集合, 而且 select();监视的事件太单一: 除了读, 写, 出错就不监视其它事件类型了。
]]

#[[

select();是以感兴趣的事件为单位来组织文件描述符(即事件发生了, 我去判断文件描述符是否
在这个事件发生的集合当中, 如果在, 就进行响应的处理。)。
而poll();不同, poll();是以文件描述符为单位等待事件(即监听的文件描述符上发生了事件后,
去判断这个事件是哪个事件, 如果是我关心的事件的话, 就执行相应的处理。)。



POLL(2)                                                                         Linux Programmer's Manual                                                                         POLL(2)

NAME
       poll, ppoll - wait for some event on a file descriptor

SYNOPSIS
       #include <poll.h>

       // fds: 这不是一个结构体的地址, 而是一个结构体数组的起始地址, 即 pollfd结构体数组。
       // 这个 pollfd数组有多少个元素就是监视多少个文件描述符fd。
       // nfds: 当前的文件描述符的个数, 第一个参数指定了一个数组, 这个参数是pollfd结构体数组有多长。
       //
      struct pollfd {
           int   fd;         /* file descriptor */
           // events字段是我关心fd上发生的事件。
           // events,是一个位图。
           short类型, 即可以监视 16 种事件。这比 select();监视的事件要详细。
           short events;     /* requested events */
           // revents字段是fd上实际发生的事件。
           // revents,也是一个位图。
           short revents;    /* returned events */
       };
       pollfd结构体用两个字段将感兴趣的事件和实际发生的事件分开存储了。

       // timeout: 是以毫秒为单位的10的-3次方, 所以, 如果 timeout设为 1000, 其实才 1 秒。
       // 如果 poll不设置 timeout 参数, poll就是死等, 阻塞的等。
       // 如果 poll();的 timeout参数传入 0, 那么就是非阻塞的调用poll();, poll会执行完后返回。
       // 如果 poll();的 timeout参数传入 -1, 那么就是阻塞的调用poll();。

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);

RETURN VALUE
       On success, a positive number is returned; this is the number of structures which have nonzero revents fields (in other words, those descriptors with events or errors  reported).
       A value of 0 indicates that the call timed out and no file descriptors were ready.  On error, -1 is returned, and errno is set appropriately.
       成功时，返回一个正数；这是具有非零 revents 字段的结构数（换句话说，那些报告了事件或错误的描述符）。
       值 0 表示调用超时并且没有准备好文件描述符。出错时，返回 -1，并适当设置 errno。

       成功时, 返回一个正数, 这个正数表示了有多少个事件发生。
       失败时, 返回 -1, 并且设置 errno。

ERRORS
       EFAULT The array given as argument was not contained in the calling program's address space.

       EINTR  A signal occurred before any requested event; see signal(7).

       EINVAL The nfds value exceeds the RLIMIT_NOFILE value.

       EINVAL (ppoll()) The timeout value expressed in *ip is invalid (negative).

       ENOMEM There was no space to allocate file descriptor tables.


]]


#[[
select();是以感兴趣的事件为单位来组织文件描述符(即事件发生了, 我去判断文件描述符是否
在这个事件发生的集合当中, 如果在, 就进行响应的处理。)。

而poll();不同, poll();是以文件描述符为单位等待事件(即监听的文件描述符上发生了事件后,
去判断这个事件是哪个事件, 如果是我关心的事件的话, 就执行相应的处理。)。

]]


#[[
使用select();poll();epoll();
1. 布置监视任务
2. 监视, 即调用 select();poll();epoll();
3. 查看监视结果, 根据结果...

使用poll();重构之前的select();函数。

重构成功了。
]]
add_executable(datarelay3 nonblock/datarelay3.cpp)


#[[
epoll();

EPOLL(7)                                                                        Linux Programmer's Manual                                                                        EPOLL(7)

NAME
       epoll - I/O event notification facility

SYNOPSIS
       #include <sys/epoll.h>

DESCRIPTION
       The  epoll  API  performs  a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them.  The epoll API can be used either as an edge-
       triggered or a level-triggered interface and scales well to large numbers of watched file descriptors.
       epoll API 执行与 poll(2) 类似的任务：监视多个文件描述符以查看是否可以在其中任何一个上进行 IO。 epoll API 可以用作边缘触发或级别触发的接口，并且可以很好地扩展到大量监视的文件描述符。

       The central concept of the epoll API is the epoll instance, an in-kernel data structure which, from a user-space perspective, can be considered as a container for two lists:
       epoll API 的核心概念是 epoll 实例，它是一种内核数据结构，从用户空间的角度来看，可以将其视为两个列表的容器:

       *   The interest list (sometimes also called the epoll set): the set of file descriptors that the process has registered an interest in monitoring.
       * 兴趣列表（有时也称为 epoll 集）: 进程已注册对监视感兴趣的文件描述符集。

       *   The ready list: the set of file descriptors that are "ready" for I/O.  The ready list is a subset of (or, more precisely, a set of references to) the file descriptors in  the
           interest list that is dynamically populated by the kernel as a result of I/O activity on those file descriptors.
       * 就绪列表: 为 IO“准备好”的一组文件描述符。就绪列表是感兴趣列表中文件描述符的子集（或更准确地说，是一组引用），由于这些文件描述符上的 IO 活动而由内核动态填充。

       The following system calls are provided to create and manage an epoll instance:
       提供了以下系统调用来创建和管理 epoll 实例:

       *  epoll_create(2)  creates  a  new  epoll  instance  and  returns  a  file descriptor referring to that instance.  (The more recent epoll_create1(2) extends the functionality of
          epoll_create(2).)
       * epoll_create(2) 创建一个新的 epoll 实例并返回一个引用该实例的文件描述符。 （最近的 epoll_create1(2) 扩展了 epoll_create(2) 的功能。）

       *  Interest in particular file descriptors is then registered via epoll_ctl(2), which adds items to the interest list of the epoll instance.
       * 然后通过 epoll_ctl(2) 注册对特定文件描述符的兴趣，它将项目添加到 epoll 实例的兴趣列表中。

       *  epoll_wait(2) waits for I/O events, blocking the calling thread if no events are currently available.  (This system call can be thought of as fetching  items  from  the  ready
          list of the epoll instance.)
       * epoll_wait(2) 等待 IO 事件，如果当前没有可用的事件，则阻塞调用线程。 （这个系统调用可以被认为是从 epoll 实例的就绪列表中获取项目。）

   Level-triggered and edge-triggered
   电平触发和边沿触发
       The  epoll  event distribution interface is able to behave both as edge-triggered (ET) and as level-triggered (LT).  The difference between the two mechanisms can be described as
       follows.  Suppose that this scenario happens:

       1. The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance.

       2. A pipe writer writes 2 kB of data on the write side of the pipe.

       3. A call to epoll_wait(2) is done that will return rfd as a ready file descriptor.

       4. The pipe reader reads 1 kB of data from rfd.

       5. A call to epoll_wait(2) is done.

       If the rfd file descriptor has been added to the epoll interface using the EPOLLET (edge-triggered) flag, the call to epoll_wait(2) done in step 5 will probably hang despite  the
       available  data  still  present  in the file input buffer; meanwhile the remote peer might be expecting a response based on the data it already sent.  The reason for this is that
       edge-triggered mode delivers events only when changes occur on the monitored file descriptor.  So, in step 5 the caller might end up waiting for some data that is already present
       inside the input buffer.  In the above example, an event on rfd will be generated because of the write done in 2 and the event is consumed in 3.  Since the read operation done in
       4 does not consume the whole buffer data, the call to epoll_wait(2) done in step 5 might block indefinitely.

       An application that employs the EPOLLET flag should use nonblocking file descriptors to avoid having a blocking read or write starve a task that is  handling  multiple  file  de‐
       scriptors.  The suggested way to use epoll as an edge-triggered (EPOLLET) interface is as follows:

              i   with nonblocking file descriptors; and

              ii  by waiting for an event only after read(2) or write(2) return EAGAIN.

       By  contrast, when used as a level-triggered interface (the default, when EPOLLET is not specified), epoll is simply a faster poll(2), and can be used wherever the latter is used
       since it shares the same semantics.

       Since even with edge-triggered epoll, multiple events can be generated upon receipt of multiple chunks of data, the caller has the option to specify  the  EPOLLONESHOT  flag,  to
       tell epoll to disable the associated file descriptor after the receipt of an event with epoll_wait(2).  When the EPOLLONESHOT flag is specified, it is the caller's responsibility
       to rearm the file descriptor using epoll_ctl(2) with EPOLL_CTL_MOD.

       If multiple threads (or processes, if child processes have inherited the epoll file descriptor across fork(2)) are blocked in epoll_wait(2) waiting on the  same  epoll  file  de‐
       scriptor  and a file descriptor in the interest list that is marked for edge-triggered (EPOLLET) notification becomes ready, just one of the threads (or processes) is awoken from
       epoll_wait(2).  This provides a useful optimization for avoiding "thundering herd" wake-ups in some scenarios.

   Interaction with autosleep
       If the system is in autosleep mode via /sys/power/autosleep and an event happens which wakes the device from sleep, the device driver will keep the device awake only  until  that
       event is queued.  To keep the device awake until the event has been processed, it is necessary to use the epoll_ctl(2) EPOLLWAKEUP flag.

       When  the  EPOLLWAKEUP flag is set in the events field for a struct epoll_event, the system will be kept awake from the moment the event is queued, through the epoll_wait(2) call
       which returns the event until the subsequent epoll_wait(2) call.  If the event should keep the system awake beyond that time, then a separate wake_lock should be taken before the
       second epoll_wait(2) call.

   /proc interfaces
       The following interfaces can be used to limit the amount of kernel memory consumed by epoll:

       /proc/sys/fs/epoll/max_user_watches (since Linux 2.6.28)
              This  specifies  a  limit  on the total number of file descriptors that a user can register across all epoll instances on the system.  The limit is per real user ID.  Each
              registered file descriptor costs roughly 90 bytes on a 32-bit kernel, and roughly 160 bytes on a 64-bit kernel.  Currently, the default value for max_user_watches is  1/25
              (4%) of the available low memory, divided by the registration cost in bytes.

   Questions and answers
       0.  What is the key used to distinguish the file descriptors registered in an interest list?

           The  key  is the combination of the file descriptor number and the open file description (also known as an "open file handle", the kernel's internal representation of an open
           file).

       1.  What happens if you register the same file descriptor on an epoll instance twice?

           You will probably get EEXIST.  However, it is possible to add a duplicate (dup(2), dup2(2), fcntl(2) F_DUPFD) file descriptor to the same epoll instance.  This can be a  use‐
           ful technique for filtering events, if the duplicate file descriptors are registered with different events masks.

       2.  Can two epoll instances wait for the same file descriptor?  If so, are events reported to both epoll file descriptors?

           Yes, and events would be reported to both.  However, careful programming may be needed to do this correctly.

       3.  Is the epoll file descriptor itself poll/epoll/selectable?

           Yes.  If an epoll file descriptor has events waiting, then it will indicate as being readable.

       4.  What happens if one attempts to put an epoll file descriptor into its own file descriptor set?

           The epoll_ctl(2) call fails (EINVAL).  However, you can add an epoll file descriptor inside another epoll file descriptor set.

       5.  Can I send an epoll file descriptor over a UNIX domain socket to another process?

           Yes, but it does not make sense to do this, since the receiving process would not have copies of the file descriptors in the interest list.

       6.  Will closing a file descriptor cause it to be removed from all epoll interest lists?

           Yes,  but  be aware of the following point.  A file descriptor is a reference to an open file description (see open(2)).  Whenever a file descriptor is duplicated via dup(2),
           dup2(2), fcntl(2) F_DUPFD, or fork(2), a new file descriptor referring to the same open file description is created.  An open file description continues to  exist  until  all
           file descriptors referring to it have been closed.

           A  file  descriptor  is removed from an interest list only after all the file descriptors referring to the underlying open file description have been closed.  This means that
           even after a file descriptor that is part of an interest list has been closed, events may be reported for that file descriptor if other file descriptors referring to the same
           underlying  file description remain open.  To prevent this happening, the file descriptor must be explicitly removed from the interest list (using epoll_ctl(2) EPOLL_CTL_DEL)
           before it is duplicated.  Alternatively, the application must ensure that all file descriptors are closed (which may be difficult if file descriptors were  duplicated  behind
           the scenes by library functions that used dup(2) or fork(2)).

       7.  If more than one event occurs between epoll_wait(2) calls, are they combined or reported separately?

           They will be combined.

       8.  Does an operation on a file descriptor affect the already collected but not yet reported events?

           You can do two operations on an existing file descriptor.  Remove would be meaningless for this case.  Modify will reread available I/O.

       9.  Do I need to continuously read/write a file descriptor until EAGAIN when using the EPOLLET flag (edge-triggered behavior)?

           Receiving  an  event  from  epoll_wait(2) should suggest to you that such file descriptor is ready for the requested I/O operation.  You must consider it ready until the next
           (nonblocking) read/write yields EAGAIN.  When and how you will use the file descriptor is entirely up to you.

           For packet/token-oriented files (e.g., datagram socket, terminal in canonical mode), the only way to detect the end of the read/write I/O space is to continue  to  read/write
           until EAGAIN.

           For stream-oriented files (e.g., pipe, FIFO, stream socket), the condition that the read/write I/O space is exhausted can also be detected by checking the amount of data read
           from / written to the target file descriptor.  For example, if you call read(2) by asking to read a certain amount of data and read(2) returns a lower number  of  bytes,  you
           can be sure of having exhausted the read I/O space for the file descriptor.  The same is true when writing using write(2).  (Avoid this latter technique if you cannot guaran‐
           tee that the monitored file descriptor always refers to a stream-oriented file.)

   Possible pitfalls and ways to avoid them
       o Starvation (edge-triggered)

       If there is a large amount of I/O space, it is possible that by trying to drain it the other files will not get processed causing starvation.  (This problem is  not  specific  to
       epoll.)

       The  solution is to maintain a ready list and mark the file descriptor as ready in its associated data structure, thereby allowing the application to remember which files need to
       be processed but still round robin amongst all the ready files.  This also supports ignoring subsequent events you receive for file descriptors that are already ready.

       o If using an event cache...

       If you use an event cache or store all the file descriptors returned from epoll_wait(2), then make sure to provide a way to mark its closure dynamically (i.e., caused by a previ‐
       ous event's processing).  Suppose you receive 100 events from epoll_wait(2), and in event #47 a condition causes event #13 to be closed.  If you remove the structure and close(2)
       the file descriptor for event #13, then your event cache might still say there are events waiting for that file descriptor causing confusion.

       One solution for this is to call, during the processing of event 47, epoll_ctl(EPOLL_CTL_DEL) to delete file descriptor 13 and close(2), then mark its associated  data  structure
       as  removed  and  link it to a cleanup list.  If you find another event for file descriptor 13 in your batch processing, you will discover the file descriptor had been previously
       removed and there will be no confusion.

VERSIONS
       The epoll API was introduced in Linux kernel 2.5.44.  Support was added to glibc in version 2.3.2.

CONFORMING TO
       The epoll API is Linux-specific.  Some other systems provide similar mechanisms, for example, FreeBSD has kqueue, and Solaris has /dev/poll.

NOTES
       The set of file descriptors that is being monitored via an epoll file descriptor can be viewed via the entry for the epoll file descriptor in the process's /proc/[pid]/fdinfo di‐
       rectory.  See proc(5) for further details.

       The kcmp(2) KCMP_EPOLL_TFD operation can be used to test whether a file descriptor is present in an epoll instance.

SEE ALSO
       epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2), poll(2), select(2)

COLOPHON
       This  page  is  part  of release 5.05 of the Linux man-pages project.  A description of the project, information about reporting bugs, and the latest version of this page, can be
       found at https://www.kernel.org/doc/man-pages/.

Linux                                                                                   2019-03-06                                                                               EPOLL(7)

]]
#[[
EPOLL_CREATE(2)                                                                 Linux Programmer's Manual                                                                 EPOLL_CREATE(2)

NAME
       epoll_create, epoll_create1 - open an epoll file descriptor
       打开一个 epoll 文件描述符。其实就是创建一个epoll实例。

SYNOPSIS
       #include <sys/epoll.h>

       // 在poll();函数中是创建了一个 pollfd 结构体数组。
       // 而epoll是内核帮我们管理了一个这类似于 poll();时的那个 pollfd结构体数组。
       // epoll_create();其实就是产生一个类似于pollfd这样的结构体数组。
       // 之前使用poll();是用户自己来维护这个数组,
       // 而epoll是内核帮我们维护这个结构体数组, 并提供epoll_ctl();epoll_wait();
       // 这些系统调用来设置这个内核维护的结构体数组。
       //
       // size参数跟我们要监视的文件描述符的数量是没有关系的。
       // size传几都可以, 给它传入一个数值就可以。
       //



       int epoll_create(int size);
       int epoll_create1(int flags);

DESCRIPTION
       epoll_create() creates a new epoll(7) instance.  Since Linux 2.6.8, the size argument is ignored, but must be greater than zero; see NOTES below.

       epoll_create()  returns  a file descriptor referring to the new epoll instance.  This file descriptor is used for all the subsequent calls to the epoll interface.  When no longer
       required, the file descriptor returned by epoll_create() should be closed by using close(2).  When all file descriptors referring to an epoll instance have been closed, the  ker‐
       nel destroys the instance and releases the associated resources for reuse.

   epoll_create1()
       If  flags is 0, then, other than the fact that the obsolete size argument is dropped, epoll_create1() is the same as epoll_create().  The following value can be included in flags
       to obtain different behavior:

       EPOLL_CLOEXEC
              Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor.  See the description of the O_CLOEXEC flag in open(2) for reasons why this may be useful.

RETURN VALUE
       On success, these system calls return a nonnegative file descriptor.  On error, -1 is returned, and errno is set to indicate the error.
       成功时，这些系统调用返回一个非负的文件描述符。出错时，返回 -1，并设置 errno 以指示错误。

       epoll_create();调用成功返回一个epoll文件描述符 epollFd。
       失败, 返回 -1, 设置 errno。

ERRORS
       EINVAL size is not positive.

       EINVAL (epoll_create1()) Invalid value specified in flags.

       EMFILE The per-user limit on the number of epoll instances imposed by /proc/sys/fs/epoll/max_user_instances was encountered.  See epoll(7) for further details.

       EMFILE The per-process limit on the number of open file descriptors has been reached.

       ENFILE The system-wide limit on the total number of open files has been reached.

       ENOMEM There was insufficient memory to create the kernel object.

VERSIONS
       epoll_create() was added to the kernel in version 2.6.  Library support is provided in glibc starting with version 2.3.2.

       epoll_create1() was added to the kernel in version 2.6.27.  Library support is provided in glibc starting with version 2.9.

CONFORMING TO
       epoll_create() is Linux-specific.

NOTES
       In the initial epoll_create() implementation, the size argument informed the kernel of the number of file descriptors that the caller expected to add to the epoll instance.   The
       kernel used this information as a hint for the amount of space to initially allocate in internal data structures describing events.  (If necessary, the kernel would allocate more
       space if the caller's usage exceeded the hint given in size.)  Nowadays, this hint is no longer required (the kernel dynamically sizes the required data structures without  need‐
       ing the hint), but size must still be greater than zero, in order to ensure backward compatibility when new epoll applications are run on older kernels.

SEE ALSO
       close(2), epoll_ctl(2), epoll_wait(2), epoll(7)

COLOPHON
       This  page  is  part  of release 5.05 of the Linux man-pages project.  A description of the project, information about reporting bugs, and the latest version of this page, can be
       found at https://www.kernel.org/doc/man-pages/.

Linux                                                                                   2017-09-15                                                                        EPOLL_CREATE(2)


EPOLL_CTL(2)                                                                    Linux Programmer's Manual                                                                    EPOLL_CTL(2)

NAME
       epoll_ctl - control interface for an epoll file descriptor
       一个epoll 文件描述符的控制接口。
       其实就是就是控制一个epoll实例。

SYNOPSIS
       #include <sys/epoll.h>

       // epfd: 其实就是 epoll_create();返回的 epollFd, 一个epoll实例, 其实就是一个文件描述符。
       // 最后记得 close(epollFd);
       // 对 epfd epoll实例进行 op 操作, 针对的文件描述符是 fd, 然后针对这个fd针对的是 event事件。
       // op可以是
      Valid values for the op argument are:

       EPOLL_CTL_ADD (添加)
              Add fd to the interest list and associate the settings specified in event with the internal file linked to fd.
              将 fd 添加到兴趣列表，并将 event 中指定的设置与链接到 fd 的内部文件相关联。

       EPOLL_CTL_MOD (修改)
              Change the settings associated with fd in the interest list to the new settings specified in event.
              将兴趣列表中与 fd 关联的设置更改为事件中指定的新设置。

       EPOLL_CTL_DEL (删除)
              Remove (deregister) the target file descriptor fd from the interest list.  The event argument is ignored and can be NULL (but see BUGS below).
              从兴趣列表中删除（注销）目标文件描述符 fd。事件参数被忽略并且可以为 NULL（但请参阅下面的 BUGS）。

       // epoll_ctl();相当于不让用户直接操作那个 epollfd结构体数组,
       // 而是使用 epoll_ctl();这个函数来操作那个结构体数组。
       // 用户通过 epoll_ctl();来操作这个结构体数组。
       // epoll内部是使用红黑树实现的。

       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

RETURN VALUE
       When successful, epoll_ctl() returns zero.  When an error occurs, epoll_ctl() returns -1 and errno is set appropriately.
       成功时，epoll_ctl() 返回零。发生错误时，epoll_ctl() 返回 -1 并正确设置 errno。


EPOLL_WAIT(2)                                                                   Linux Programmer's Manual                                                                   EPOLL_WAIT(2)

NAME
       epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor
       等待 epoll 文件描述符上的 IO 事件。
       epoll_wait();是往外取发生了的事件。

SYNOPSIS
       #include <sys/epoll.h>

       // 在 epfd 这个epoll实例中, 取事件, 可以连续取多个事件。
       // 第二个参数是一个 epoll_event 结构体数组的起始地址,
       // 第三个参数是这个 epoll_event 结构体数组的大小。
       // 然后 epoll_wait():就是一次性取 maxevnts 个事件, 放到 events结构体数组中去。
       //
       // 然后 epoll_wait();也有一个超时设置。和poll();的超时设置是一样的。
       // 以毫秒为单位, 传入 1000, 即 1 秒。
       // 如果设置为 -1, 即是阻塞的。
       // 如果设置为 0, 即是非阻塞的。

       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
       int epoll_pwait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout,
                      const sigset_t *sigmask);

RETURN VALUE
       When successful, epoll_wait() returns the number of file descriptors ready for the requested I/O, or zero if no file descriptor became ready during  the  requested  timeout  mil‐
       liseconds.  When an error occurs, epoll_wait() returns -1 and errno is set appropriately.
       成功时，epoll_wait() 返回为请求的 IO 准备好的文件描述符的数量，如果在请求的超时毫秒内没有文件描述符准备好，则返回零。发生错误时，epoll_wait() 返回 -1 并正确设置 errno。

       readyFds

ERRORS
       EBADF  epfd is not a valid file descriptor.

       EFAULT The memory area pointed to by events is not accessible with write permissions.

       EINTR  The call was interrupted by a signal handler before either (1) any of the requested events occurred or (2) the timeout expired; see signal(7).

       EINVAL epfd is not an epoll file descriptor, or maxevents is less than or equal to zero.


]]

#[[
使用select();poll();epoll();
1. 布置监视任务
2. 监视, 即调用 select();poll();epoll();
3. 查看监视结果, 根据结果...
]]
#[[
用 epoll 来重构之前 poll 重构的 relay程序。

重构完成, 没有问题。重构成功。
]]
add_executable(datarelay4 nonblock/datarelay4.cpp)

#[[
关于 select(); poll(); epoll(); 这三个函数复习记得看 游双老师的书。

]]


#[[
其它读写函数, 其它高级IO函数。

readv();
writev();

READV(2)                                                                        Linux Programmer's Manual                                                                        READV(2)

NAME
       readv, writev, preadv, pwritev, preadv2, pwritev2 - read or write data into multiple buffers
       读取或写入数据到多个缓冲区。

SYNOPSIS
       #include <sys/uio.h>


        struct iovec {
            // 起始地址
            void  *iov_base;    /* Starting address */
            // 要传输的字节数(即起始地址的那块空间的大小。)
            size_t iov_len;     /* Number of bytes to transfer */
        };

       // readv();和writev();的第二个参数要的不是一个结构体的地址,
       // 而是一个结构体数组的起始地址！
       // 第三个参数指定了这个结构体数组有多长, 即指定了第二个参数的
       // 结构体数组有多长。

       // 例如, 我要写一个文件, 可是我要写的文件的内容的数据来源不是
       // 一块连续的空间, 可能这块空间要拿10个字节写到文件中去,
       // 在另外一块空间拿100个字节写到文件中去。


       ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

       ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

       ssize_t preadv(int fd, const struct iovec *iov, int iovcnt,
                      off_t offset);

       ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt,
                       off_t offset);

       ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt,
                       off_t offset, int flags);

       ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt,
                        off_t offset, int flags);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       preadv(), pwritev():
           Since glibc 2.19:
               _DEFAULT_SOURCE
           Glibc 2.19 and earlier:
               _BSD_SOURCE

DESCRIPTION
       The readv() system call reads iovcnt buffers from the file associated with the file descriptor fd into the buffers described by iov ("scatter input").
       readv()系统调用 从与文件描述符 fd 关联的文件中读取 iovcnt 缓冲区到由 iov（“分散输入”）描述的缓冲区中。

       The writev() system call writes iovcnt buffers of data described by iov to the file associated with the file descriptor fd ("gather output").
       writev()系统调用 将 iov 描述的数据的 iovcnt 缓冲区写入与文件描述符 fd 关联的文件（“收集输出”）。

       The pointer iov points to an array of iovec structures, defined in <sys/uio.h> as:
       指针 iov 指向一个 iovec 结构数组，在 <sysuio.h> 中定义为:

           struct iovec {
               // 起始地址
               void  *iov_base;    /* Starting address */
               // 要传输的字节数
               size_t iov_len;     /* Number of bytes to transfer */
           };

       The readv() system call works just like read(2) except that multiple buffers are filled.
       readv() 系统调用的工作方式与 read(2) 类似，只是填充了多个缓冲区。

       The writev() system call works just like write(2) except that multiple buffers are written out.
       writev() 系统调用的工作方式与 write(2) 类似，只是写出多个缓冲区。

       Buffers  are processed in array order.  This means that readv() completely fills iov[0] before proceeding to iov[1], and so on.  (If there is insufficient data, then not all buf‐
       fers pointed to by iov may be filled.)  Similarly, writev() writes out the entire contents of iov[0] before proceeding to iov[1], and so on.
       缓冲区按数组顺序处理。这意味着 readv() 在继续执行 iov[1] 之前完全填充了 iov[0]，依此类推。
       （如果数据不足，则不是所有 iov 指向的缓冲区都可以填满。）同样，writev() 在继续执行 iov[1] 之前写出 iov[0] 的全部内容，依此类推。

       The data transfers performed by readv() and writev() are atomic: the data written by writev() is written as a single block that is not intermingled with  output  from  writes  in
       other  processes  (but see pipe(7) for an exception); analogously, readv() is guaranteed to read a contiguous block of data from the file, regardless of read operations performed
       in other threads or processes that have file descriptors referring to the same open file description (see open(2)).
       readv() 和 writev() 执行的数据传输是原子的：writev() 写入的数据作为单个块写入，不会与其他进程中写入的输出混合（但请参阅 pipe(7) 以了解异常）;
       类似地，readv() 保证从文件中读取连续的数据块，而不管在其他线程或进程中执行的读取操作如何，这些线程或进程的文件描述符引用相同的打开文件描述（请参阅 open(2)）。

   preadv() and pwritev()
       The preadv() system call combines the functionality of readv() and pread(2).  It performs the same task as readv(), but adds a fourth argument, offset, which specifies  the  file
       offset at which the input operation is to be performed.

       The  pwritev()  system  call combines the functionality of writev() and pwrite(2).  It performs the same task as writev(), but adds a fourth argument, offset, which specifies the
       file offset at which the output operation is to be performed.

       The file offset is not changed by these system calls.  The file referred to by fd must be capable of seeking.

   preadv2() and pwritev2()
       These system calls are similar to preadv() and pwritev() calls, but add a fifth argument, flags, which modifies the behavior on a per-call basis.

       Unlike preadv() and pwritev(), if the offset argument is -1, then the current file offset is used and updated.

       The flags argument contains a bitwise OR of zero or more of the following flags:

       RWF_DSYNC (since Linux 4.7)
              Provide a per-write equivalent of the O_DSYNC open(2) flag.  This flag is meaningful only for pwritev2(), and its effect applies only to the data range written by the sys‐
              tem call.

       RWF_HIPRI (since Linux 4.6)
              High  priority  read/write.  Allows block-based filesystems to use polling of the device, which provides lower latency, but may use additional resources.  (Currently, this
              feature is usable only on a file descriptor opened using the O_DIRECT flag.)

       RWF_SYNC (since Linux 4.7)
              Provide a per-write equivalent of the O_SYNC open(2) flag.  This flag is meaningful only for pwritev2(), and its effect applies only to the data range written by the  sys‐
              tem call.

       RWF_NOWAIT (since Linux 4.14)
              Do not wait for data which is not immediately available.  If this flag is specified, the preadv2() system call will return instantly if it would have to read data from the
              backing storage or wait for a lock.  If some data was successfully read, it will return the number of bytes read.  If no bytes were read, it will return -1 and  set  errno
              to EAGAIN.  Currently, this flag is meaningful only for preadv2().

       RWF_APPEND (since Linux 4.16)
              Provide  a  per-write  equivalent  of the O_APPEND open(2) flag.  This flag is meaningful only for pwritev2(), and its effect applies only to the data range written by the
              system call.  The offset argument does not affect the write operation; the data is always appended to the end of the file.  However, if the offset argument is -1, the cur‐
              rent file offset is updated.

RETURN VALUE
       On success, readv(), preadv() and preadv2() return the number of bytes read; writev(), pwritev() and pwritev2() return the number of bytes written.
       成功时，readv()、preadv() 和 preadv2() 返回读取的字节数； writev()、pwritev() 和 pwritev2() 返回写入的字节数。

       Note that it is not an error for a successful call to transfer fewer bytes than requested (see read(2) and write(2)).
       请注意，成功调用传输的字节数少于请求的字节数并不是错误（请参阅 read(2) 和 write(2)）。
       todo: 笔记: 所以在调用 writev();函数的时候, 和 write();一样, 一定要设计坚持读够多少个字节。

       On error, -1 is returned, and errno is set appropriately.
       出错时，返回 -1，并适当设置 errno。

ERRORS
       The  errors are as given for read(2) and write(2).  Furthermore, preadv(), preadv2(), pwritev(), and pwritev2() can also fail for the same reasons as lseek(2).  Additionally, the
       following errors are defined:

       EINVAL The sum of the iov_len values overflows an ssize_t value.

       EINVAL The vector count, iovcnt, is less than zero or greater than the permitted maximum.

       EOPNOTSUPP
              An unknown flag is specified in flags.

VERSIONS
       preadv() and pwritev() first appeared in Linux 2.6.30; library support was added in glibc 2.10.

       preadv2() and pwritev2() first appeared in Linux 4.6.  Library support was added in glibc 2.26.

CONFORMING TO
       readv(), writev(): POSIX.1-2001, POSIX.1-2008, 4.4BSD (these system calls first appeared in 4.2BSD).

       preadv(), pwritev(): nonstandard, but present also on the modern BSDs.

       preadv2(), pwritev2(): nonstandard Linux extension.

NOTES
       POSIX.1 allows an implementation to place a limit on the number of items that can be passed in iov.  An implementation can advertise its limit by defining IOV_MAX  in  <limits.h>
       or at run time via the return value from sysconf(_SC_IOV_MAX).  On modern Linux systems, the limit is 1024.  Back in Linux 2.0 days, this limit was 16.
       POSIX.1 允许实现对可以在 iov 中传递的项目数量进行限制。
       实现可以通过在 <limits.h> 中定义 IOV_MAX 或在运行时通过来自 sysconf(_SC_IOV_MAX) 的返回值来通告其限制。
       在现代 Linux 系统上，限制是 1024。在 Linux 2.0 天，这个限制是 16。

   C library/kernel differences
       The  raw  preadv() and pwritev() system calls have call signatures that differ slightly from that of the corresponding GNU C library wrapper functions shown in the SYNOPSIS.  The
       final argument, offset, is unpacked by the wrapper functions into two arguments in the system calls:

           unsigned long pos_l, unsigned long pos

       These arguments contain, respectively, the low order and high order 32 bits of offset.

   Historical C library/kernel differences
       To deal with the fact that IOV_MAX was so low on early versions of Linux, the glibc wrapper functions for readv() and writev() did some extra work if they detected that  the  un‐
       derlying  kernel  system call failed because this limit was exceeded.  In the case of readv(), the wrapper function allocated a temporary buffer large enough for all of the items
       specified by iov, passed that buffer in a call to read(2), copied data from the buffer to the locations specified by the iov_base fields of the elements of iov,  and  then  freed
       the buffer.  The wrapper function for writev() performed the analogous task using a temporary buffer and a call to write(2).

       The need for this extra effort in the glibc wrapper functions went away with Linux 2.2 and later.  However, glibc continued to provide this behavior until version 2.10.  Starting
       with glibc version 2.9, the wrapper functions provide this behavior only if the library detects that the system is running a Linux kernel older than version 2.6.18 (an  arbitrar‐
       ily  selected kernel version).  And since glibc 2.20 (which requires a minimum Linux kernel version of 2.6.32), the glibc wrapper functions always just directly invoke the system
       calls.

EXAMPLE
       The following code sample demonstrates the use of writev():

           char *str0 = "hello ";
           char *str1 = "world\n";
           struct iovec iov[2];
           ssize_t nwritten;

           iov[0].iov_base = str0;
           iov[0].iov_len = strlen(str0);
           iov[1].iov_base = str1;
           iov[1].iov_len = strlen(str1);

           nwritten = writev(STDOUT_FILENO, iov, 2);

SEE ALSO
       pread(2), read(2), write(2)

COLOPHON
       This page is part of release 5.05 of the Linux man-pages project.  A description of the project, information about reporting bugs, and the latest version of  this  page,  can  be
       found at https://www.kernel.org/doc/man-pages/.

Linux                                                                                   2018-04-30                                                                               READV(2)

]]

#[[
坚持读够多少个字节, 坚持写够多少个字节。一般来说, 会设计一个while()循环来坚持写够多少个字节。而读多少个字节就是: 读多少个那么就是多少个。

]]

#[[
存储映射IO
mmap(); (memory map: 内存映射)
把某一块内存或者把某一个文件的内容映射到当前的进程空间内存中来,
你在当前进程当中访问, 一段char类型的内存, 就如同访问那块内存或者那个文件一样。

MMAP(2)                                                                         Linux Programmer's Manual                                                                         MMAP(2)

NAME
       mmap, munmap - map or unmap files or devices into memory
       将文件或设备映射或取消映射到内存中。

SYNOPSIS
       #include <sys/mman.h>

       // addr: 你把一块空间(文件或设备)映射到内存中放到哪个地址起始。
       // length: 映射多长的内容到内存中。
       //
       // 把 addr 置为 nullptr, 传入nullptr意味着:
       // 有mmap();来帮我们找一块可用的地址空间。
       //
       // prot: 对映射过来的内存做什么样的操作。
       // flags: 是一个位图, 必选:
       // MAP_SHARED: 你对这块空间的修改, 别人可以访问的到。别人也可以访问这块空间。
       // MAP_PRIVATE: 私有的。
       // 中一个。
       // 另外常用的 flags 有一个是 MAP_ANONYMOUS, 匿名映射。
       // 使用 MAP_ANONYMOUS 相当于 malloc();
       // 如果使用匿名映射, 那么下一个参数 fd 要置为 -1。
       //
       //


       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);


       // munmap();将这块内存释放掉。
       // 如果mmap();采用匿名的话, 那么 munmap();相当于一个 free();的动作。


       int munmap(void *addr, size_t length);

       See NOTES for information on feature test macro requirements.

DESCRIPTION
       mmap()  creates  a new mapping in the virtual address space of the calling process.  The starting address for the new mapping is specified in addr.  The length argument specifies
       the length of the mapping (which must be greater than 0).

       If addr is NULL, then the kernel chooses the (page-aligned) address at which to create the mapping; this is the most portable method of creating a new mapping.  If  addr  is  not
       NULL, then the kernel takes it as a hint about where to place the mapping; on Linux, the kernel will pick a nearby page boundary (but always above or equal to the value specified
       by /proc/sys/vm/mmap_min_addr) and attempt to create the mapping there.  If another mapping already exists there, the kernel picks a new address that may or may not depend on the
       hint.  The address of the new mapping is returned as the result of the call.

       The  contents  of a file mapping (as opposed to an anonymous mapping; see MAP_ANONYMOUS below), are initialized using length bytes starting at offset offset in the file (or other
       object) referred to by the file descriptor fd.  offset must be a multiple of the page size as returned by sysconf(_SC_PAGE_SIZE).

       After the mmap() call has returned, the file descriptor, fd, can be closed immediately without invalidating the mapping.

       The prot argument describes the desired memory protection of the mapping (and must not conflict with the open mode of the file).  It is either PROT_NONE or the bitwise OR of  one
       or more of the following flags:

       PROT_EXEC  Pages may be executed.

       PROT_READ  Pages may be read.

       PROT_WRITE Pages may be written.

       PROT_NONE  Pages may not be accessed.

       The  flags  argument  determines  whether updates to the mapping are visible to other processes mapping the same region, and whether updates are carried through to the underlying
       file.  This behavior is determined by including exactly one of the following values in flags:

       MAP_SHARED
              Share this mapping.  Updates to the mapping are visible to other processes mapping the same region, and (in the case of file-backed mappings) are carried  through  to  the
              underlying file.  (To precisely control when updates are carried through to the underlying file requires the use of msync(2).)

       MAP_SHARED_VALIDATE (since Linux 4.15)
              This  flag  provides  the  same  behavior  as  MAP_SHARED  except  that  MAP_SHARED  mappings  ignore  unknown  flags in flags.  By contrast, when creating a mapping using
              MAP_SHARED_VALIDATE, the kernel verifies all passed flags are known and fails the mapping with the error EOPNOTSUPP for unknown flags.  This mapping type is also  required
              to be able to use some mapping flags (e.g., MAP_SYNC).

       MAP_PRIVATE
              Create  a  private  copy-on-write  mapping.  Updates to the mapping are not visible to other processes mapping the same file, and are not carried through to the underlying
              file.  It is unspecified whether changes made to the file after the mmap() call are visible in the mapped region.

       Both MAP_SHARED and MAP_PRIVATE are described in POSIX.1-2001 and POSIX.1-2008.  MAP_SHARED_VALIDATE is a Linux extension.

       In addition, zero or more of the following values can be ORed in flags:

       MAP_32BIT (since Linux 2.4.20, 2.6)
              Put the mapping into the first 2 Gigabytes of the process address space.  This flag is supported only on x86-64, for 64-bit programs.  It was added to allow thread  stacks
              to  be  allocated  somewhere  in the first 2 GB of memory, so as to improve context-switch performance on some early 64-bit processors.  Modern x86-64 processors no longer
              have this performance problem, so use of this flag is not required on those systems.  The MAP_32BIT flag is ignored when MAP_FIXED is set.

       MAP_ANON
              Synonym for MAP_ANONYMOUS.  Deprecated.

       MAP_ANONYMOUS
              The mapping is not backed by any file; its contents are initialized to zero.  The fd argument is ignored; however, some implementations require fd to be -1  if  MAP_ANONY‐
              MOUS  (or  MAP_ANON)  is  specified,  and  portable  applications  should  ensure  this.  The offset argument should be zero.  The use of MAP_ANONYMOUS in conjunction with
              MAP_SHARED is supported on Linux only since kernel 2.4.
              映射不受任何文件的支持；它的内容被初始化为零。 fd 参数被忽略；然而，如果指定了 MAP_ANONYMOUS（或 MAP_ANON），一些实现要求 fd 为 -1，并且便携式应用程序应该确保这一点。 offset 参数应该为零。 MAP_ANONYMOUS 与 MAP_SHARED 结合使用仅在 Linux 内核 2.4 后才支持。

       MAP_DENYWRITE
              This flag is ignored.  (Long ago—Linux 2.0 and earlier—it signaled that attempts to write to the underlying file should fail with ETXTBUSY.  But this was a source  of  de‐
              nial-of-service attacks.)

       MAP_EXECUTABLE
              This flag is ignored.

       MAP_FILE
              Compatibility flag.  Ignored.

       MAP_FIXED
              Don't  interpret  addr  as  a hint: place the mapping at exactly that address.  addr must be suitably aligned: for most architectures a multiple of the page size is suffi‐
              cient; however, some architectures may impose additional restrictions.  If the memory region specified by addr and len overlaps pages of any existing mapping(s), then  the
              overlapped part of the existing mapping(s) will be discarded.  If the specified address cannot be used, mmap() will fail.

              Software  that  aspires  to  be portable should use the MAP_FIXED flag with care, keeping in mind that the exact layout of a process's memory mappings is allowed to change
              significantly between kernel versions, C library versions, and operating system releases.  Carefully read the discussion of this flag in NOTES!

       MAP_FIXED_NOREPLACE (since Linux 4.17)
              This flag provides behavior that is similar to MAP_FIXED with respect to the addr enforcement, but differs in that MAP_FIXED_NOREPLACE never clobbers a preexisting  mapped
              range.   If the requested range would collide with an existing mapping, then this call fails with the error EEXIST.  This flag can therefore be used as a way to atomically
              (with respect to other threads) attempt to map an address range: one thread will succeed; all others will report failure.

              Note that older kernels which do not recognize the MAP_FIXED_NOREPLACE flag will typically (upon detecting a collision with a preexisting mapping) fall  back  to  a  "non-
              MAP_FIXED"  type  of behavior: they will return an address that is different from the requested address.  Therefore, backward-compatible software should check the returned
              address against the requested address.

       MAP_GROWSDOWN
              This flag is used for stacks.  It indicates to the kernel virtual memory system that the mapping should extend downward in memory.  The return address is  one  page  lower
              than  the memory area that is actually created in the process's virtual address space.  Touching an address in the "guard" page below the mapping will cause the mapping to
              grow by a page.  This growth can be repeated until the mapping grows to within a page of the high end of the next lower mapping, at which point touching the  "guard"  page
              will result in a SIGSEGV signal.

       MAP_HUGETLB (since Linux 2.6.32)
              Allocate the mapping using "huge pages."  See the Linux kernel source file Documentation/admin-guide/mm/hugetlbpage.rst for further information, as well as NOTES, below.

       MAP_HUGE_2MB, MAP_HUGE_1GB (since Linux 3.8)
              Used in conjunction with MAP_HUGETLB to select alternative hugetlb page sizes (respectively, 2 MB and 1 GB) on systems that support multiple hugetlb page sizes.

              More  generally,  the  desired huge page size can be configured by encoding the base-2 logarithm of the desired page size in the six bits at the offset MAP_HUGE_SHIFT.  (A
              value of zero in this bit field provides the default huge page size; the default huge page size can be discovered via the Hugepagesize  field  exposed  by  /proc/meminfo.)
              Thus, the above two constants are defined as:

                  #define MAP_HUGE_2MB    (21 << MAP_HUGE_SHIFT)
                  #define MAP_HUGE_1GB    (30 << MAP_HUGE_SHIFT)

              The range of huge page sizes that are supported by the system can be discovered by listing the subdirectories in /sys/kernel/mm/hugepages.

       MAP_LOCKED (since Linux 2.5.37)
              Mark the mapped region to be locked in the same way as mlock(2).  This implementation will try to populate (prefault) the whole range but the mmap() call doesn't fail with
              ENOMEM if this fails.  Therefore major faults might happen later on.  So the semantic is not as strong as mlock(2).  One should use mmap() plus mlock(2) when major  faults
              are not acceptable after the initialization of the mapping.  The MAP_LOCKED flag is ignored in older kernels.

       MAP_NONBLOCK (since Linux 2.5.46)
              This flag is meaningful only in conjunction with MAP_POPULATE.  Don't perform read-ahead: create page tables entries only for pages that are already present in RAM.  Since
              Linux 2.6.23, this flag causes MAP_POPULATE to do nothing.  One day, the combination of MAP_POPULATE and MAP_NONBLOCK may be reimplemented.

       MAP_NORESERVE
              Do not reserve swap space for this mapping.  When swap space is reserved, one has the guarantee that it is possible to modify the mapping.  When swap space is not reserved
              one  might  get SIGSEGV upon a write if no physical memory is available.  See also the discussion of the file /proc/sys/vm/overcommit_memory in proc(5).  In kernels before
              2.6, this flag had effect only for private writable mappings.

       MAP_POPULATE (since Linux 2.5.46)
              Populate (prefault) page tables for a mapping.  For a file mapping, this causes read-ahead on the file.  This will help to reduce blocking on page faults later.  MAP_POPU‐
              LATE is supported for private mappings only since Linux 2.6.23.

       MAP_STACK (since Linux 2.6.27)
              Allocate the mapping at an address suitable for a process or thread stack.

              This flag is currently a no-op on Linux.  However, by employing this flag, applications can ensure that they transparently obtain support if the flag is implemented in the
              future.  Thus, it is used in the glibc threading implementation to allow for the fact that some architectures may (later) require special treatment for stack  allocations.
              A further reason to employ this flag is portability: MAP_STACK exists (and has an effect) on some other systems (e.g., some of the BSDs).

       MAP_SYNC (since Linux 4.15)
              This  flag  is available only with the MAP_SHARED_VALIDATE mapping type; mappings of type MAP_SHARED will silently ignore this flag.  This flag is supported only for files
              supporting DAX (direct mapping of persistent memory).  For other files, creating a mapping with this flag results in an EOPNOTSUPP error.

              Shared file mappings with this flag provide the guarantee that while some memory is writably mapped in the address space of the process, it will be  visible  in  the  same
              file  at  the  same offset even after the system crashes or is rebooted.  In conjunction with the use of appropriate CPU instructions, this provides users of such mappings
              with a more efficient way of making data modifications persistent.

       MAP_UNINITIALIZED (since Linux 2.6.33)
              Don't clear anonymous pages.  This flag is intended to improve performance on embedded devices.  This flag is honored only if the  kernel  was  configured  with  the  CON‐
              FIG_MMAP_ALLOW_UNINITIALIZED  option.  Because of the security implications, that option is normally enabled only on embedded devices (i.e., devices where one has complete
              control of the contents of user memory).

       Of the above flags, only MAP_FIXED is specified in POSIX.1-2001 and POSIX.1-2008.  However, most systems also support MAP_ANONYMOUS (or its synonym MAP_ANON).

       Memory mapped by mmap() is preserved across fork(2), with the same attributes.

       A file is mapped in multiples of the page size.  For a file that is not a multiple of the page size, the remaining memory is zeroed when mapped, and writes to that region are not
       written out to the file.  The effect of changing the size of the underlying file of a mapping on the pages that correspond to added or removed regions of the file is unspecified.

   munmap()
       The  munmap() system call deletes the mappings for the specified address range, and causes further references to addresses within the range to generate invalid memory references.
       The region is also automatically unmapped when the process is terminated.  On the other hand, closing the file descriptor does not unmap the region.

       The address addr must be a multiple of the page size (but length need not be).  All pages containing a part of the indicated range are  unmapped,  and  subsequent  references  to
       these pages will generate SIGSEGV.  It is not an error if the indicated range does not contain any mapped pages.

RETURN VALUE
       On success, mmap() returns a pointer to the mapped area.  On error, the value MAP_FAILED (that is, (void *) -1) is returned, and errno is set to indicate the cause of the error.

       On success, munmap() returns 0.  On failure, it returns -1, and errno is set to indicate the cause of the error (probably to EINVAL).

]]
# 使用 mmap(); 来计算一个文件中有多少个字符 a 。
add_executable(advancedIO-mmap io/AdvancedIo-mmap.cpp)
# 父子进程间通过 mmap(); 进行通信。
add_executable(advancedIO-mmap2 io/AdvancedIo-mmap2.cpp)


#[[
文件锁
fcntl();
lockf();
flock();
]]





















